﻿#region Usings
using System;
using System.Windows;
using System.Windows.Markup;
using System.Collections.ObjectModel;
#endregion

namespace RootLibrary.WPF.Localization {

    #region LocalizeExtension
    /// <summary>
    /// Represents a LocalizationExtension which provides a localized object of a .resx dictionary
    /// </summary>
    /// <remarks>
    /// If a content between two tags in xaml is set, this has the higher priority and will overwrite the setted properties
    /// </remarks>
    [MarkupExtensionReturnType(typeof(object))]
    [ContentProperty("ResourceIdentifierKey")]
    public abstract class LocalizeExtension : MarkupExtension, IWeakEventListener {

        #region Members
        /// <summary>
        /// Holds the unique id for this instance.
        /// This is only used for better debugging.
        /// </summary>
        private Guid id = Guid.NewGuid();

        /// <summary>
        /// Holds the Name of the .resx dictionary.
        /// If it's null, "Resources" will get returned
        /// </summary>
        private string m_Dict;

        /// <summary>
        /// Holds the name of the Assembly where the .resx is located
        /// </summary>
        private string m_Assembly;

        /// <summary>
        /// Holds the Key to a .resx object
        /// </summary>
        private string m_Key;

        /// <summary>
        /// Holds the collection of assigned dependency objects as WeakReferences
        /// </summary>
        private Collection<WeakReference> m_TargetObjects;
        #endregion

        #region Properties
        /// <summary>
        /// Holds the Key that identifies a resource (Assembly:Dictionary:Key)
        /// </summary>
        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
        public string ResourceIdentifierKey {
            get { return string.Format("{0}:{1}:{2}", Assembly, Dict, Key ?? "(null)"); }
            set { LocalizeDictionary.ParseKey(value, out m_Assembly, out m_Dict, out m_Key); }
        }

        /// <summary>
        /// Holds the culture to force a fixed localized object
        /// </summary>
        public string ForceCulture { get; set; }

        /// <summary>
        /// Holds the Key to a .resx object
        /// </summary>
        public string Key {
            get { return m_Key; }
            set { m_Key = value; }
        }

        /// <summary>
        /// Holds the name of the Assembly where the .resx is located.
        /// If it's null, the executing assembly (where this LocalizeEngine is located at) will get returned
        /// </summary>
        public string Assembly {
            get { return m_Assembly ?? LocalizeDictionary.Instance.GetAssemblyName(System.Reflection.Assembly.GetExecutingAssembly()); }
            set { m_Assembly = !string.IsNullOrEmpty(value) ? value : null; }
        }

        /// <summary>
        /// Holds the Name of the .resx dictionary.
        /// If it's null, "Resources" will get returned
        /// </summary>
        public string Dict {
            get { return m_Dict ?? LocalizeDictionary.ResourcesName; }
            set { m_Dict = !string.IsNullOrEmpty(value) ? value : null; }
        }
        #endregion

        #region Dependency Properties
        /// <summary>
        /// The DependencyProperty which should get the localized content
        /// </summary>
        public DependencyProperty TargetProperty { get; private set; }
        /// <summary>
        /// The collection of DependencyObject as WeakReferences which contains the TargetProperty
        /// </summary>
        public ReadOnlyCollection<WeakReference> TargetObjects { get { return new ReadOnlyCollection<WeakReference>(m_TargetObjects); } }
        #endregion

        #region Constructor
        /// <summary>
        /// Initialize the LocalizeExtension
        /// </summary>
        protected LocalizeExtension() {
            // initialize the collection of the assigned dependency objects
            m_TargetObjects = new Collection<WeakReference>();
        }

        /// <summary>
        /// Initialize the LocalizeExtension
        /// </summary>
        /// <remarks>
        /// This constructor register the Eventhandler <c>OnCultureChanged</c> on <c>LocalizeDictionary</c>
        /// to get an aknowledge of changing the culture
        /// </remarks>
        /// <param name="key">
        /// Three types are supported:
        /// Direct: passed key = key; 
        /// Dict/Key pair: this have to be seperated like ResXDictionaryName:ResourceKey
        /// Assembly/Dict/Key pair: this have to be seperated like ResXDictionaryName:ResourceKey
        /// </param>
        protected LocalizeExtension(string key)
            : this() {
            // parse the key value and split it up if necessary
            LocalizeDictionary.ParseKey(key, out m_Assembly, out m_Dict, out m_Key);
        }
        #endregion

        #region GetForcedCultureOrDefault
        /// <summary>
        /// If Culture property defines a valid CultureInfo, a CultureInfo instance will get 
        /// created and returned, otherwise LocalizeDictionary.Culture will get returned.
        /// </summary>
        /// <returns>The CultureInfo</returns>
        /// <exception cref="System.ArgumentException">thrown if the parameter Culture dont defines a valid CultureInfo</exception>
        protected System.Globalization.CultureInfo GetForcedCultureOrDefault() {
            System.Globalization.CultureInfo cultureInfo = null;

            // try to create a valid cultureinfo, if defined
            if (!string.IsNullOrEmpty(ForceCulture)) {
                try {
                    // try to create a specific culture from the forced one
                    cultureInfo = System.Globalization.CultureInfo.CreateSpecificCulture(ForceCulture);
                }
                catch (ArgumentException ex) {
                    // on error, check if designmode is on
                    if (LocalizeDictionary.Instance.GetIsInDesignMode()) {
                        // cultureInfo will be set to the current specific culture
                        cultureInfo = LocalizeDictionary.Instance.SpecificCulture;
                    }
                    else {
                        // tell the customer, that the forced culture cannot be converted propperly
                        throw new ArgumentException("Cannot create a CultureInfo with '" + ForceCulture + "'", ex);
                    }
                }
            }
            // elsewise, take the current specific culture
            else {
                cultureInfo = LocalizeDictionary.Instance.SpecificCulture;
            }

            // return the evaluated culture info
            return cultureInfo;
        }
        #endregion

        #region SetNewValue
        /// <summary>
        /// Set the Value of the DependencyProperty to the passed Value
        /// </summary>
        /// <param name="newValue">The new Value</param>
        protected void SetNewValue(object newValue) {
            // if the list of dependency objects is empty or the target property is null, return
            if (m_TargetObjects.Count == 0 || TargetProperty == null) return;

            // step through all dependency objects as WeakReference and refresh the value of the dependency property
            foreach (WeakReference dpo in m_TargetObjects) {
                // set the new value of the target, if the target DependencyTarget is still alive
                if (dpo.IsAlive) {
                    ((DependencyObject)dpo.Target).SetValue(TargetProperty, newValue);
                }
            }
        }
        #endregion

        #region ProvideValue
        /// <summary>
        /// Provides the Value for the first Binding
        /// </summary>
        /// <remarks>
        /// This method register the Eventhandler <c>OnCultureChanged</c> on <c>LocalizeDictionary</c>
        /// to get an aknowledge of changing the culture, if the passed TargetObjects type of DependencyObject.
        /// 
        /// !PROOF: On every single UserControl, Window, and Page, 
        /// there is a new SparedDP reference, and so there is every time a new LocalizeExtension!
        /// Because of this, we dont need to notify every single DependencyObjects to update their value (for GC).
        /// </remarks>
        /// <param name="serviceProvider">
        /// The <see cref="System.Windows.Markup.IProvideValueTarget"/> provided from the MarkupExtension
        /// </param>
        /// <returns>The founded item from the .resx directory or null if not founded</returns>
        /// <exception cref="System.InvalidOperationException">
        /// thrown if <paramref name="serviceProvider"/> is not type of <see cref="System.Windows.Markup.IProvideValueTarget"/>
        /// </exception>
        public override object ProvideValue(IServiceProvider serviceProvider) {
            // try to cast the passed serviceProvider to a IProvideValueTarget
            IProvideValueTarget service = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            // if the cast fails, an exception will be thrown. (should never happen)
            if (service == null) {
                throw new InvalidOperationException("IProvideValueTarget service is unavailable");
            }

            // if the service.TargetObject is a Binding, throw an exception
            if (service.TargetObject is System.Windows.Data.Binding) {
                throw new InvalidOperationException("Use as binding is not supported!");
            }

            // try to cast the service.TargetProperty as DependencyProperty
            TargetProperty = service.TargetProperty as DependencyProperty;

            // if this fails, return this (should never happen)
            if (TargetProperty == null) {
                return this;
            }

            // indicates, if the target object was found
            bool foundInWeakReferences = false;
            // search for the target in the target object list
            foreach (WeakReference wr in m_TargetObjects) {
                // if the target is the target of the weakreference
                if (wr.Target == service.TargetObject) {
                    // set the flag to true and stop searching
                    foundInWeakReferences = true;
                    break;
                }
            }

            // if the target is a dependency object and it's not collected already, collect it
            if (service.TargetObject is DependencyObject && !foundInWeakReferences) {

                // if it's the first object, add an event handler too
                if (m_TargetObjects.Count == 0) {
                    // add this localize extension to the WeakEventManager on LocalizeDictionary
                    LocalizeDictionary.Instance.AddEventListener(this);
                }

                // add the target as an dependency object as weakreference to the dependency object list
                m_TargetObjects.Add(new WeakReference(service.TargetObject));

                // adds this localize extension to the ObjectDependencyManager to ensure the lifetime along with the targetobject
                ObjectDependencyManager.AddObjectDependency(new WeakReference(service.TargetObject), this);
            }

            // if the service.TargetObject is System.Windows.SharedDp (= not a DependencyObject), we return "this".
            // the SharedDp will call this instance later again.
            if (!(service.TargetObject is DependencyObject)) {
                // by returning "this", the provide value will be called later again.
                return this;
            }

            // return the new value for the DependencyProperty
            return LocalizeDictionary.Instance.GetLocizedObject<object>(Assembly, Dict, Key, GetForcedCultureOrDefault());
        }
        #endregion

        #region SetBinding
        /// <summary>
        /// Sets a binding between a DependencyObject with its DependencyProperty and this LocalizeExtension
        /// </summary>
        /// <param name="targetObject">The target dependency object</param>
        /// <param name="targetProperty">The target dependency property</param>
        /// <returns>TRUE if the binding was setup successfully, otherwise FALSE (Binding already exists).</returns>
        public bool SetBinding(DependencyObject targetObject, DependencyProperty targetProperty) {
            // indicates, if the target object was found
            bool foundInWeakReferences = false;
            // search for the target in the target object list
            foreach (WeakReference wr in m_TargetObjects) {
                // if the target is the target of the weakreference
                if (wr.Target == targetObject) {
                    // set the flag to true and stop searching
                    foundInWeakReferences = true;
                    break;
                }
            }

            // set the TargetProperty to the passed one
            TargetProperty = targetProperty;

            // if the target it's not collected already, collect it
            if (!foundInWeakReferences) {

                // if it's the first object, add an event handler too
                if (m_TargetObjects.Count == 0) {
                    // add this localize extension to the WeakEventManager on LocalizeDictionary
                    LocalizeDictionary.Instance.AddEventListener(this);
                }

                // add the target as an dependency object as weakreference to the dependency object list
                m_TargetObjects.Add(new WeakReference(targetObject));

                // adds this localize extension to the ObjectDependencyManager to ensure the lifetime along with the targetobject
                ObjectDependencyManager.AddObjectDependency(new WeakReference(targetObject), this);

                // set the initial value of the dependency property
                targetObject.SetValue(TargetProperty, FormatOutput(LocalizeDictionary.Instance.GetLocizedObject<object>(Assembly, Dict, Key, GetForcedCultureOrDefault())));

                // return true, the binding was successfully
                return true;
            }
            else {
                // return false, the binding already exists
                return false;
            }
        }
        #endregion

        #region Abstract FormatOutput
        /// <summary>
        /// This method is used to modify the passed object into the target format
        /// </summary>
        /// <param name="input">The object that will be modified</param>
        /// <returns>Returns the modified object</returns>
        protected abstract object FormatOutput(object input);
        #endregion

        #region HandleNewValue
        /// <summary>
        /// This method gets the new value for the target property and call SetNewValue.
        /// </summary>
        protected virtual void HandleNewValue() {
            // gets the new value and set it to the dependency property on the dependency object
            SetNewValue(LocalizeDictionary.Instance.GetLocizedObject<object>(Assembly, Dict, Key, GetForcedCultureOrDefault()));
        }
        #endregion

        #region IWeakEventListener Members

        /// <summary>
        /// This method will be called through the interface, passed to the 
        /// LocalizeDictionary.WeakCultureChangedEventManager to get notifyed  on culture changed
        /// </summary>
        bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) {
            // if the passed handler is type of LocalizeDictionary.WeakCultureChangedEventManager, handle it
            if (managerType == typeof(LocalizeDictionary.WeakCultureChangedEventManager)) {
                // call to handle the new value
                HandleNewValue();
                // return true, to notify the event was processed
                return true;
            }
            // return false, to notify the event was not processed
            return false;
        }

        #endregion

        #region Destructor
        /// <summary>
        /// This destructor only exists fr debug reasons
        /// </summary>
        ~LocalizeExtension() {
            //#if DEBUG
            //            System.Diagnostics.Debug.WriteLine("LocExtension destructed!");
            //#endif
        }
        #endregion

        #region ToString()
        /// <summary>
        /// Returns the Key that identifies a resource (Assembly:Dictionary:Key)
        /// </summary>
        /// <returns>Format: Assembly:Dictionary:Key</returns>
        public sealed override string ToString() {
            return ResourceIdentifierKey;
        }
        #endregion
    }
    #endregion

    #region LocText
    /// <summary>
    /// LocalizeExtension for string objects
    /// </summary>
    [MarkupExtensionReturnType(typeof(string))]
    public class LocText : LocalizeExtension {

        #region Enums
        /// <summary>
        /// This enum is used to determine the type 
        /// of the return value of GetAppendText
        /// </summary>
        protected enum AppendType {
            /// <summary>
            /// The return value is used as prefix
            /// </summary>
            Prefix,
            /// <summary>
            /// The return value is used as suffix
            /// </summary>
            Suffix
        }
        #endregion

        #region Members
        private string m_Prefix;
        private string m_Suffix;
        #endregion

        #region Properties
        /// <summary>
        /// Defines a prefix for the localized text
        /// </summary>
        public string Prefix {
            get { return m_Prefix; }
            set {
                m_Prefix = value;
                // reset the value of the target property
                HandleNewValue();
            }
        }

        /// <summary>
        /// Defines a suffix fot the localized text
        /// </summary>
        public string Suffix {
            get { return m_Suffix; }
            set {
                m_Suffix = value;
                // reset the value of the target property
                HandleNewValue();
            }
        }

        /// <summary>
        /// Defines, if a space should be added between 
        /// the prefix and the localized text or not
        /// </summary>
        public bool PrefixSpace { get; set; }

        /// <summary>
        /// Defines, if a space should be added between 
        /// the suffix and the localized text or not
        /// </summary>
        public bool SuffixSpace { get; set; }
        #endregion

        #region Constructor
        /// <summary>
        /// <see cref="LocalizeExtension()"/>
        /// </summary>
        public LocText() : base() { }
        /// <summary>
        /// <see cref="LocalizeExtension(string)"/>
        /// </summary>
        public LocText(string key) : base(key) { }
        #endregion

        #region GetAppendText
        /// <summary>
        /// Returns the prefix or suffix text, depending on the supplied AppendType.
        /// If the prefix or suffix is null or empty, the space option will be ignored.
        /// </summary>
        /// <param name="at">The AppendType defindes the format of the return value</param>
        /// <returns>Returns the formated prefix or suffix</returns>
        private string GetAppendText(AppendType at) {
            // define a return value
            string retVal = string.Empty;

            // if it should be a prefix, the format will be [PREFIX[SPACE]]
            if (at == AppendType.Prefix && !string.IsNullOrEmpty(m_Prefix)) {
                retVal = m_Prefix + (PrefixSpace ? " " : string.Empty);
            }
            // if it should be a suffix, the format will be [[SPACE]SUFFIX]
            else if (at == AppendType.Suffix && !string.IsNullOrEmpty(m_Suffix)) {
                retVal = (PrefixSpace ? " " : string.Empty) + m_Suffix;
            }

            // return the formated prefix or suffix
            return retVal;
        }
        #endregion

        #region FormatText
        /// <summary>
        /// This method formats the localized text.
        /// If the passed target text is null, string.empty will be returned.
        /// </summary>
        /// <param name="target">The text to format.</param>
        /// <returns>Returns the formated text or string.empty, if the target text was null.</returns>
        protected virtual string FormatText(string target) {
            return target ?? string.Empty;
        }
        #endregion

        #region ProvideValue
        /// <summary>
        /// Provides the Value for the first Binding as <see cref="System.String"/>
        /// </summary>
        /// <param name="serviceProvider">
        /// The <see cref="System.Windows.Markup.IProvideValueTarget"/> provided from the MarkupExtension
        /// </param>
        /// <returns>The founded item from the .resx directory or null if not founded</returns>
        /// <exception cref="System.InvalidOperationException">
        /// thrown if <paramref name="serviceProvider"/> is not type of <see cref="System.Windows.Markup.IProvideValueTarget"/>
        /// </exception>
        /// <exception cref="System.NotSupportedException">
        /// thrown if the founded object is not type of <see cref="System.String"/>
        /// </exception>
        public override object ProvideValue(IServiceProvider serviceProvider) {
            object obj = base.ProvideValue(serviceProvider);
            if (obj == null) return null;
            if (obj is LocalizeExtension) return obj;
            if (obj.GetType().Equals(typeof(string))) {
                return FormatOutput(obj);
            }
            else {
                throw new NotSupportedException(
                    string.Format("ResourceKey '{0}' returns '{1}' which is not type of System.String",
                    Key, obj.GetType().FullName));
            }
        }
        #endregion

        #region override HandleNewValue
        /// <summary>
        /// <see cref="LocalizeExtension.HandleNewValue()"/>
        /// </summary>
        protected override void HandleNewValue() {
            SetNewValue(FormatOutput(null));
        }
        #endregion

        #region FormatOutput
        /// <summary>
        /// This method returns the finished formatted text
        /// </summary>
        /// <param name="input">If the passed string not null, it will be used, otherwise a fresh localized text will be loaded.</param>
        /// <returns>Returns the finished formatted text in format [PREFIX[SPACE]]LocalizedText[[SPACE]SUFFIX]</returns>
        protected override object FormatOutput(object input) {
            // load a fresh localized text, if the passed string is null
            input = input ?? LocalizeDictionary.Instance.GetLocizedObject<object>(Assembly, Dict, Key, GetForcedCultureOrDefault());

            // format the text with prefix and suffix to [PREFIX[SPACE]]LocalizedText[[SPACE]SUFFIX]
            input = FormatText(GetAppendText(AppendType.Prefix)) + FormatText((string)input) + FormatText(GetAppendText(AppendType.Suffix));

            // return the finished formatted text
            return input;
        }
        #endregion
    }
    #endregion

    #region LocTextUpper
    /// <summary>
    /// LocalizeExtension for string objects.
    /// This strings will be converted to upper case
    /// </summary>
    [MarkupExtensionReturnType(typeof(string))]
    public class LocTextUpper : LocText {

        #region Constructor
        /// <summary>
        /// <see cref="LocalizeExtension()"/>
        /// </summary>
        public LocTextUpper() : base() { }
        /// <summary>
        /// <see cref="LocalizeExtension(string)"/>
        /// </summary>
        public LocTextUpper(string key) : base(key) { }
        #endregion

        #region FormatText
        /// <summary>
        /// <see cref="LocText.FormatText(string)"/>
        /// </summary>
        protected override string FormatText(string target) {
            return target.ToUpper(GetForcedCultureOrDefault()).Replace("ß", "SS");
        }
        #endregion

        #region ProvideValue
        /// <summary>
        /// Provides the Value for the first Binding as <see cref="System.String"/>
        /// </summary>
        /// <param name="serviceProvider">
        /// The <see cref="System.Windows.Markup.IProvideValueTarget"/> provided from the MarkupExtension
        /// </param>
        /// <returns>The founded item from the .resx directory or null if not founded</returns>
        /// <exception cref="System.InvalidOperationException">
        /// thrown if <paramref name="serviceProvider"/> is not type of <see cref="System.Windows.Markup.IProvideValueTarget"/>
        /// </exception>
        /// <exception cref="System.NotSupportedException">
        /// thrown if the founded object is not type of <see cref="System.String"/>
        /// </exception>
        public override object ProvideValue(IServiceProvider serviceProvider) {
            object obj = base.ProvideValue(serviceProvider);
            if (obj == null) return null;
            if (obj is LocalizeExtension) return obj;
            if (obj.GetType().Equals(typeof(string))) {
                // dont call GetLocalizedText at this point, otherwise you will get prefix and suffix twice appended
                return obj;
            }
            else {
                throw new NotSupportedException(
                    string.Format("ResourceKey '{0}' returns '{1}' which is not type of System.String",
                    Key, obj.GetType().FullName));
            }
        }
        #endregion

        #region override HandleNewValue
        /// <summary>
        /// <see cref="LocalizeExtension.HandleNewValue()"/>
        /// </summary>
        protected override void HandleNewValue() {
            object obj = LocalizeDictionary.Instance.GetLocizedObject<object>(Assembly, Dict, Key, GetForcedCultureOrDefault());
            SetNewValue(FormatOutput(obj));
        }
        #endregion
    }
    #endregion

    #region LocTextLower
    /// <summary>
    /// LocalizeExtension for string objects.
    /// This strings will be converted to lower case
    /// </summary>
    [MarkupExtensionReturnType(typeof(string))]
    public class LocTextLower : LocText {

        #region Constructor
        /// <summary>
        /// <see cref="LocalizeExtension()"/>
        /// </summary>
        public LocTextLower() : base() { }
        /// <summary>
        /// <see cref="LocalizeExtension(string)"/>
        /// </summary>
        public LocTextLower(string key) : base(key) { }
        #endregion

        #region FormatText
        /// <summary>
        /// <see cref="LocText.FormatText(string)"/>
        /// </summary>
        protected override string FormatText(string target) {
            return target.ToLower(GetForcedCultureOrDefault());
        }
        #endregion

        #region ProvideValue
        /// <summary>
        /// Provides the Value for the first Binding as <see cref="System.String"/>
        /// </summary>
        /// <param name="serviceProvider">
        /// The <see cref="System.Windows.Markup.IProvideValueTarget"/> provided from the MarkupExtension
        /// </param>
        /// <returns>The founded item from the .resx directory or null if not founded</returns>
        /// <exception cref="System.InvalidOperationException">
        /// thrown if <paramref name="serviceProvider"/> is not type of <see cref="System.Windows.Markup.IProvideValueTarget"/>
        /// </exception>
        /// <exception cref="System.NotSupportedException">
        /// thrown if the founded object is not type of <see cref="System.String"/>
        /// </exception>
        public override object ProvideValue(IServiceProvider serviceProvider) {
            object obj = base.ProvideValue(serviceProvider);
            if (obj == null) return null;
            if (obj is LocalizeExtension) return obj;
            if (obj.GetType().Equals(typeof(string))) {
                // dont call GetLocalizedText at this point, otherwise you will get prefix and suffix twice appended
                return obj;
            }
            else {
                throw new NotSupportedException(
                    string.Format("ResourceKey '{0}' returns '{1}' which is not type of System.String",
                    Key, obj.GetType().FullName));
            }
        }
        #endregion

        #region override HandleNewValue
        /// <summary>
        /// <see cref="LocalizeExtension.HandleNewValue()"/>
        /// </summary>
        protected override void HandleNewValue() {
            object obj = LocalizeDictionary.Instance.GetLocizedObject<object>(Assembly, Dict, Key, GetForcedCultureOrDefault());
            SetNewValue(FormatOutput(obj));
        }
        #endregion
    }
    #endregion

    #region LocImage
    /// <summary>
    /// LocalizeExtension for image objects
    /// </summary>
    [MarkupExtensionReturnType(typeof(System.Windows.Media.Imaging.BitmapSource))]
    public class LocImage : LocalizeExtension {

        #region Constructor
        /// <summary>
        /// <see cref="LocalizeExtension()"/>
        /// </summary>
        public LocImage() : base() { }
        /// <summary>
        /// <see cref="LocalizeExtension(string)"/>
        /// </summary>
        public LocImage(string key) : base(key) { }
        #endregion

        #region ProvideValue
        /// <summary>
        /// Provides the Value for the first Binding as <see cref="System.Windows.Media.Imaging.BitmapSource"/>
        /// </summary>
        /// <param name="serviceProvider">
        /// The <see cref="System.Windows.Markup.IProvideValueTarget"/> provided from the MarkupExtension
        /// </param>
        /// <returns>The founded item from the .resx directory or null if not founded</returns>
        /// <exception cref="System.InvalidOperationException">
        /// thrown if <paramref name="serviceProvider"/> is not type of <see cref="System.Windows.Markup.IProvideValueTarget"/>
        /// </exception>
        /// <exception cref="System.NotSupportedException">
        /// thrown if the founded object is not type of <see cref="System.Drawing.Bitmap"/>
        /// </exception>
        public override object ProvideValue(IServiceProvider serviceProvider) {
            object obj = base.ProvideValue(serviceProvider);
            if (obj == null) return null;
            if (obj is LocalizeExtension) return obj;
            if (obj.GetType().Equals(typeof(System.Drawing.Bitmap))) {
                return FormatOutput(obj);
            }
            else {
                throw new NotSupportedException(
                    string.Format("ResourceKey '{0}' returns '{1}' which is not type of System.Drawing.Bitmap",
                    Key, obj.GetType().FullName));
            }
        }
        #endregion

        #region override HandleNewValue
        /// <summary>
        /// <see cref="LocalizeExtension.HandleNewValue()"/>
        /// </summary>
        protected override void HandleNewValue() {
            object obj = LocalizeDictionary.Instance.GetLocizedObject<object>(Assembly, Dict, Key, GetForcedCultureOrDefault());
            SetNewValue(FormatOutput(obj));
        }
        #endregion

        #region InterOp DeleteObject
        /// <summary>
        /// free memory of a pointer
        /// </summary>
        /// <param name="o">object to remove from memory</param>
        /// <returns>0 if ok, otherwise another number</returns>
        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        private static extern int DeleteObject(IntPtr o);
        #endregion

        #region FormatOutput
        /// <summary>
        /// Creates a <see cref="System.Windows.Media.Imaging.BitmapSource"/> from a <see cref="System.Drawing.Bitmap"/>
        /// </summary>
        /// <param name="input">The <see cref="System.Drawing.Bitmap"/> to convert</param>
        /// <returns>The converted <see cref="System.Windows.Media.Imaging.BitmapSource"/></returns>
        protected override object FormatOutput(object input) {
            // allocate the memory for the bitmap
            IntPtr bmpPt = ((System.Drawing.Bitmap)input).GetHbitmap();

            // create the bitmapSource
            System.Windows.Media.Imaging.BitmapSource bitmapSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                bmpPt,
                IntPtr.Zero,
                Int32Rect.Empty,
                System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());

            // freeze the bitmap to avoid hooking events to the bitmap
            bitmapSource.Freeze();

            // free memory
            DeleteObject(bmpPt);

            // return bitmapSource
            return bitmapSource;
        }
        #endregion
    }
    #endregion

    #region LocBrush
    /// <summary>
    /// LocalizeExtension for brush objects as string (uses TypeConverter)
    /// </summary>
    [MarkupExtensionReturnType(typeof(System.Windows.Media.Brush))]
    public class LocBrush : LocalizeExtension {

        #region Constructor
        /// <summary>
        /// <see cref="LocalizeExtension()"/>
        /// </summary>
        public LocBrush() : base() { }
        /// <summary>
        /// <see cref="LocalizeExtension(string)"/>
        /// </summary>
        public LocBrush(string key) : base(key) { }
        #endregion

        #region ProvideValue
        /// <summary>
        /// Provides the Value for the first Binding as <see cref="System.Windows.Media.Brush"/>
        /// </summary>
        /// <param name="serviceProvider">
        /// The <see cref="System.Windows.Markup.IProvideValueTarget"/> provided from the MarkupExtension
        /// </param>
        /// <returns>The founded item from the .resx directory or null if not founded</returns>
        /// <exception cref="System.InvalidOperationException">
        /// thrown if <paramref name="serviceProvider"/> is not type of <see cref="System.Windows.Markup.IProvideValueTarget"/>
        /// </exception>
        /// <exception cref="System.NotSupportedException">
        /// thrown if the founded object is not type of <see cref="System.String"/>
        /// </exception>
        /// <exception cref="System.NotSupportedException">
        /// The founded resource-string cannot be converted into the appropriate object.
        /// </exception>
        public override object ProvideValue(IServiceProvider serviceProvider) {
            object obj = base.ProvideValue(serviceProvider);
            if (obj == null) return null;
            if (obj is LocalizeExtension) return obj;
            if (obj.GetType().Equals(typeof(string))) {
                return FormatOutput(obj);
            }
            else {
                throw new NotSupportedException(
                    string.Format("ResourceKey '{0}' returns '{1}' which is not type of System.Drawing.Bitmap",
                    Key, obj.GetType().FullName));
            }
        }
        #endregion

        #region override HandleNewValue
        /// <summary>
        /// <see cref="LocalizeExtension.HandleNewValue()"/>
        /// </summary>
        protected override void HandleNewValue() {
            object obj = LocalizeDictionary.Instance.GetLocizedObject<object>(Assembly, Dict, Key, GetForcedCultureOrDefault());
            SetNewValue(new System.Windows.Media.BrushConverter().ConvertFromString((string)obj));
        }
        #endregion

        #region FormatOutput
        protected override object FormatOutput(object input) {
            return new System.Windows.Media.BrushConverter().ConvertFromString((string)input);
        }
        #endregion
    }
    #endregion

    #region LocDouble
    /// <summary>
    /// LocalizeExtension for double values
    /// </summary>
    [MarkupExtensionReturnType(typeof(double))]
    public class LocDouble : LocalizeExtension {

        #region Constructor
        /// <summary>
        /// <see cref="LocalizeExtension()"/>
        /// </summary>
        public LocDouble() : base() { }
        /// <summary>
        /// <see cref="LocalizeExtension(string)"/>
        /// </summary>
        public LocDouble(string key) : base(key) { }
        #endregion

        #region ProvideValue
        /// <summary>
        /// Provides the Value for the first Binding as double
        /// </summary>
        /// <param name="serviceProvider">
        /// The <see cref="System.Windows.Markup.IProvideValueTarget"/> provided from the MarkupExtension
        /// </param>
        /// <returns>The founded item from the .resx directory or null if not founded</returns>
        /// <exception cref="System.InvalidOperationException">
        /// thrown if <paramref name="serviceProvider"/> is not type of <see cref="System.Windows.Markup.IProvideValueTarget"/>
        /// </exception>
        /// <exception cref="System.NotSupportedException">
        /// thrown if the founded object is not type of double
        /// </exception>
        public override object ProvideValue(IServiceProvider serviceProvider) {
            object obj = base.ProvideValue(serviceProvider);
            if (obj == null) return null;
            if (obj is LocalizeExtension) return obj;
            if (obj.GetType().Equals(typeof(string))) {
                return FormatOutput(obj);
            }
            else {
                throw new NotSupportedException(
                    string.Format("ResourceKey '{0}' returns '{1}' which is not type of double",
                    Key, obj.GetType().FullName));
            }
        }
        #endregion

        #region override HandleNewValue
        /// <summary>
        /// <see cref="LocalizeExtension.HandleNewValue()"/>
        /// </summary>
        protected override void HandleNewValue() {
            object obj = LocalizeDictionary.Instance.GetLocizedObject<object>(Assembly, Dict, Key, GetForcedCultureOrDefault());
            SetNewValue(FormatOutput(obj));
        }
        #endregion

        #region FormatOutput
        protected override object FormatOutput(object input) {
            return double.Parse((string)input, new System.Globalization.CultureInfo("en-US"));

            //System.Reflection.MethodInfo method = typeof(System.ComponentModel.DoubleConverter).GetMethod("FromString", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
            //object result = method.Invoke(null, new object[] { source, new System.Globalization.CultureInfo("en-US") });

            //return (double)result;
        }
        #endregion
    }
    #endregion

    #region LocThickness
    /// <summary>
    /// LocalizeExtension for Thickness values
    /// </summary>
    [MarkupExtensionReturnType(typeof(Thickness))]
    public class LocThickness : LocalizeExtension {

        #region Constructor
        /// <summary>
        /// <see cref="LocalizeExtension()"/>
        /// </summary>
        public LocThickness() : base() { }
        /// <summary>
        /// <see cref="LocalizeExtension(string)"/>
        /// </summary>
        public LocThickness(string key) : base(key) { }
        #endregion

        #region ProvideValue
        /// <summary>
        /// Provides the Value for the first Binding as Thickness
        /// </summary>
        /// <param name="serviceProvider">
        /// The <see cref="System.Windows.Markup.IProvideValueTarget"/> provided from the MarkupExtension
        /// </param>
        /// <returns>The founded item from the .resx directory or null if not founded</returns>
        /// <exception cref="System.InvalidOperationException">
        /// thrown if <paramref name="serviceProvider"/> is not type of <see cref="System.Windows.Markup.IProvideValueTarget"/>
        /// </exception>
        /// <exception cref="System.NotSupportedException">
        /// thrown if the founded object is not type of Thickness
        /// </exception>
        public override object ProvideValue(IServiceProvider serviceProvider) {
            object obj = base.ProvideValue(serviceProvider);
            if (obj == null) return null;
            if (obj is LocalizeExtension) return obj;
            if (obj.GetType().Equals(typeof(string))) {
                return FormatOutput(obj);
            }
            else {
                throw new NotSupportedException(
                    string.Format("ResourceKey '{0}' returns '{1}' which is not type of double",
                    Key, obj.GetType().FullName));
            }
        }
        #endregion

        #region override HandleNewValue
        /// <summary>
        /// <see cref="LocalizeExtension.HandleNewValue()"/>
        /// </summary>
        protected override void HandleNewValue() {
            object obj = LocalizeDictionary.Instance.GetLocizedObject<object>(Assembly, Dict, Key, GetForcedCultureOrDefault());
            SetNewValue(FormatOutput(obj));
        }
        #endregion

        #region FormatOutput
        protected override object FormatOutput(object input) {
            System.Reflection.MethodInfo method = typeof(ThicknessConverter).GetMethod("FromString", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
            object result = method.Invoke(null, new object[] { (string)input, new System.Globalization.CultureInfo("en-US") });

            return (Thickness)result;
        }
        #endregion
    }
    #endregion
}